#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Common.h"
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include <cerrno>
#include <unistd.h>
#include "InetAddr.hpp"
#include <sys/wait.h>
#include <pthread.h>
#include <functional>
#include "ThreadPool.hpp"
using namespace LogModule;
static const uint16_t gport = 8080;
using handler_t = std::function<std::string(std::string&)>;//为什么这里是引用呢
using namespace ThreadPoolModule;

#define BACKLOG 8
class TcpServer
{
    using task_t = std::function<void()>;

    struct ThreadData
    {
        int sockfd;
        TcpServer *self;
    };

public:
    TcpServer(handler_t handler, int port = gport) : _port(port), _isrunning(false), _handler(handler)
    {
    }
    void InitServer()
    {
        // 1.创建TCP  socket
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = ::htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        // 2.绑定
        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd is : " << _listensockfd;
        // 3.cs,tcp是面向连接的，客户端就要求服务器随时随地等待被连接
        // tcp 需要将socket设置为监听状态
        n = ::listen(_listensockfd, BACKLOG); // 这里不会阻塞，只是改变了一个状态
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success, sockfd is : " << _listensockfd;

        //::signal(SIGCHLD, SIG_IGN); // 显示忽略子进程结束信号，这样子进程退出后，OS会自动回收资源，父进程就不用wait了
    }
    void HandlerRequest(int sockfd) // TCP也是全双工通信的
    {
        char buffer[4096];
        std::string package;
        while (true)
        {
            // 约定：用户发过来的是一个完整的命令string
            //  从文件描述符里读
            ssize_t n = ::recv(sockfd, buffer, sizeof(buffer) - 1, 0); // read读取其实是不完善的
            if (n > 0)
            {
                buffer[n] = 0;
                LOG(LogLevel::INFO) << "recv data from client: " << buffer;

                package += buffer;//这里的循环+=，是为了，如果不是一个完整报文，那么_handler处理肯定会失败
                                  //cmd_result就会是一个空串，然后在下面的if判断中会执行continue
                                  //执行下一次循环，也就是，如果报文不完整，那么就会继续下一次循环读取，把读取到的内容在原来的基础上加到package后面，再解码，重复步骤，直到能解码成功

                std::string cmd_result = _handler(package);
                if (cmd_result.size() == 0)
                    continue;
                ::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0);
            }
            else if (n == 0)
            {
                // 如果read读取的返回值为0，表示客户端关闭
                LOG(LogLevel::INFO) << "client close the connection " << sockfd;
                break;
            }
            else
            {
                // 表示读取失败
                LOG(LogLevel::WARNING) << "read error: " << strerror(errno);
                break;
            }
        }
        // 走到这里，表示客户端连接已经结束，当前的这个连接里的文件描述符就没有用了，所以要及时关闭，防止泄露
        close(sockfd);
    }
    static void *ThreadEntry(void *args) // 如果不设置成static的，那么该函数是一个类的成员函数，参数列表里默认有一个this指针，但是我们不想要这个this指针充当一个参数，所以设置成static的
    {
        pthread_detach(pthread_self()); // 分离线程，防止出现类似僵尸进程的情况
        ThreadData *data = (ThreadData *)args;
        // 但是如果设置成static的，那么就不能用this指针，所以就不能调用成员函数
        // 我们可以通过在类内再设置一个结构体，在这个结构体设置一个指针变量，来保存this，然后通过pthread_create传进来，这样就得到了this指针
        data->self->HandlerRequest(data->sockfd);
        return nullptr;
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // tcp服务器刚开始不能直接读数据
            // 1.获取新连接
            struct sockaddr_in peer;
            socklen_t peerlen = sizeof(peer); // 这个长度是peer的长度，不能没有初始化
            // 我们要获取client的信息：数据（sockfd）+ client socket信息（accept || recvfrom）
            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error: " << strerror(errno);
                continue;
            }
            // 获取连接成功
            LOG(LogLevel::INFO) << "accept success, sockfd is : " << sockfd;
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "client info: " << addr.Addr();
            // version --0
            // HandlerRequest(sockfd);

            // version --1 多进程版本
            // pid_t id = fork();
            // if (id == 0) // 交给子进程
            // {
            //     // 子进程可以继承父进程的文件描述符表，父子进程，文件描述符表一共有两张，父子各一张
            //     // 也就是子进程可以看到父进程的listen套接字，但是它不需要listen套接字
            //     // 所以子进程需要关闭继承自父进程的监听套接字，防止父子进程互相影响
            //     ::close(_listensockfd);
            //     if(fork() > 0) exit(0);//这里是子进程退出
            //     HandlerRequest(sockfd);//孙子进程来执行任务，但是子进程已经退出了，孙子进程就变成了孤儿进程，会被1号进程自动回收
            //     //这样，就连signal(SIGCHLD, SIG_IGN)也不需要了
            //     exit(0);
            // }
            // 父进程只负责获取新连接，由子进程来处理请求，所以父进程应该关闭它自己不关心的文件描述符（已经继承给子进程由它来处理了）
            //::close(sockfd); // 这样就相当于父进程变相的把自己的文件描述符交给了子进程，由子进程来统一处理
            // 这里子进程把listensockfd关闭了，不会影响父进程的listensockfd，因为有引用计数，父进程关闭文件描述符同理
            // 如果子进程处理的是一个长任务，很长时间没有退出吗，那父进程就在这里阻塞等待了
            // 阻塞了之后，就无法进入下一次循环，无法再次获取新连接，然后交给子进程处理
            // 这不就相当于还是单进程吗，我们可以通过signal(SIGCHLD, SIG_IGN)解决
            // int rid = ::waitpid(id, nullptr, 0);
            // if (rid < 0)
            // {
            //     LOG(LogLevel::WARNING) << "waitpid error: " << strerror(errno);
            // }

            // version --2 多线程版本
            // 主线程和新线程如何看待：文件描述符表--------它们共享一张文件描述符表,所以主线程或者新线程都不需要关闭fd
            // pthread_t tid;
            // pthread_create(&tid, nullptr, HandlerRequest, &sockfd);这样直接把sockfd的地址传进去是不对的
            // 因为这个新线程可能还没执行，而主线程又进入了下一次循环，相当于旧的sockfd局部变量已经被释放了，那么新线程拿到的就是个野指针
            // ThreadData* data = new ThreadData;//这样，就可以避免上述情况，即即使外面的sockfd被释放了，也不会影响到这个
            // data->sockfd = sockfd;
            // data->self = this;//保存this指针
            // pthread_create(&tid, nullptr, ThreadEntry, data);

            // version --3 线程池版本,比较适合处理短任务，或着是用户量少的情况
            task_t f = std::bind(&TcpServer::HandlerRequest, this, sockfd); // 构建任务
            // 第一种方式：bind方式
            ThreadPool<task_t>::GetInstance()->Equeue(f);
            // 第二种方式：lambda表达式方式
            // ThreadPool<task_t>::GetInstance()->Equeue([this, sockfd](){ return this->HandlerRequest(sockfd);});
        }
    }
    void Stop()
    {
        _isrunning = false;
    }
    ~TcpServer()
    {
    }

private:
    int _listensockfd; // 监听套接字
    uint16_t _port;
    bool _isrunning;

    // 处理上层任务的入口
    handler_t _handler;
};